package com.ecr.hub.database.dao.impl;

import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.ecr.hub.database.DaoCopyException;
import com.ecr.hub.database.dao.IDaoSillon;
import com.ecr.hub.database.mapper.MapperSillon;
import com.ecr.hub.model.enumeration.HubObjectActive;
import com.ecr.hub.model.enumeration.HubSillonStatus;
import com.ecr.hub.model.enumeration.HubSillonType;
import com.ecr.hub.model.sillon.Sillon;

@Repository("daoSillon")
public class DaoSillonImpl extends MapperSillon implements IDaoSillon {

	// Récupération de toutes les données des sillons.
	private static final String REQ_SELECT = "SELECT * FROM hub_sillon";

	// Récupération des sillons pour une plage de dates donnée + plage horaire.
	private static final String REQ_SELECT_BY_DATE = REQ_SELECT
			+ " WHERE movement_date>=? AND movement_date<=? "
			+ " AND movement_time>=? AND movement_time<=?"
			+ " ORDER BY movement_date ASC, movement_time ASC";

	// Récupération des sillons (ciblé départs / arrivées) pour une plage de
	// dates donnée + une plage horaire.
	private static final String REQ_SELECT_BY_DATE_TYPE = REQ_SELECT
			+ " WHERE movement_date>=? AND movement_date<=? "
			+ " AND movement_time>=? AND movement_time<=? AND type=?"
			+ " ORDER BY movement_date ASC, movement_time ASC";

	// Récupération de toutes les données du sillon à partir de l'id interne du
	// sillon.
	private static final String REQ_SELECT_BY_ID = "SELECT * FROM hub_sillon as sillon "
			+ "WHERE sillon.id=?";

	// Récupération de toutes les données du sillon à partir de l'id externe et
	// de la date du sillon.
	private static final String REQ_SELECT_BY_EXT_ID_DATE = "SELECT * FROM hub_sillon "
			+ "WHERE ext_id=? AND (movement_date=? OR real_movement_date=?)";

	// Récupération des données sillon à partir du TCMS TRAIN ID.
	private static final String REQ_SELECT_BY_TCMS_TRAIN_ID = "SELECT * FROM hub_sillon "
			+ "WHERE tcms_train_id=?";

	// Création d'un nouveau sillon. (Intégration sillon).
	private static final String REQ_INSERT = "INSERT INTO hub_sillon (ext_id, movement_date, movement_time,"
			+ " real_movement_date, real_movement_time, tcms_train_id, origin, destination, status_id, "
			+ " type, nb_wagons, ecr_owner, comment) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)";

	// Mise à jour d'un sillon à partir du TCMS TRAIN ID. (Intégration sillon).
	private static final String REQ_UPDATE_BY_TCMS_TRAIN_ID = "UPDATE hub_sillon SET movement_date=?, "
			+ "movement_time=?, tcms_train_id=?, origin=?, destination=?, ext_id=?, nb_wagons=?, "
			+ "status_id=?, type=?, real_movement_date=?, real_movement_time=?, updated=?, ecr_owner=?, "
			+ "comment=? WHERE tcms_train_id=?";

	// Récupération de tous les sillons pour la gestion des arrivées.
	private static final String REQ_SELECT_ARRIVAL = REQ_SELECT
			+ " WHERE status_id < "
			+ HubSillonStatus.ARRIVAL_COMPLETED.getValue() + " AND  type='"
			+ HubSillonType.ARRIVAL.getValue() + "'"
			+ " ORDER BY movement_date ASC, movement_time ASC";

	// Récupération de tous les sillons pour la gestion des départs.
	private static final String REQ_SELECT_DEPARTURE = REQ_SELECT
			+ " WHERE (status_id > " + HubSillonStatus.CREATED.getValue()
			+ " AND status_id < "
			+ HubSillonStatus.DEPARTURE_COMPLETED.getValue() + ")"
			+ " AND type='" + HubSillonType.DEPARTURE.getValue() + "'"
			+ " ORDER BY movement_date ASC, movement_time ASC";

	// Mise à jour du commentaire pour un sillon à partir de son id interne.
	private static final String REQ_UPDATE_COMMENT = "UPDATE hub_sillon SET comment=? WHERE id=?";

	// Mise à jour d'un sillon (hors commentaire) à partir de son id interne.
	private static final String REQ_UPDATE = "UPDATE hub_sillon SET status_id=?, reverse_sense=?, rail_id=?, rail_ext_id=?, "
			+ "real_movement_date=?, real_movement_time=?, rail_formation_id=?, rail_formation_ext_id=?, "
			+ "nb_wagons=?, lading=? , rail_label=?, rail_formation_label=?, "
			+ "compo_file_name=?, ecr_owner=? WHERE id=?";

	// Historisation de la table des sillons (sécurité suplémentaire avec les
	// statuts.) (1/3).
	private static final String REQ_HISTO_SELECT = "SELECT * FROM hub_sillon WHERE movement_date <= ? AND status_id="
			+ HubSillonStatus.ARRIVAL_COMPLETED.getValue()
			+ " OR status_id="
			+ HubSillonStatus.DEPARTURE_COMPLETED.getValue()
			+ " OR status_id="
			+ HubSillonStatus.CANCELED.getValue();

	// Historisation de la table des sillons (2/3).
	private static final String REQ_HISTO_MOVE = "INSERT INTO hub_histo_sillon VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

	// Historisation de la table des sillons (3/3).
	private static final String REQ_HISTO_DEL = "DELETE FROM hub_sillon WHERE id=?";

	@Autowired
	JdbcTemplate jdbcTemplate;

	/**
	 * Récupération de l'ensemble de la liste des sillons, quel que soit le
	 * statut ou le type du sillon (départ ou arrivée).
	 */
	@Override
	public List<Sillon> getAll() throws DataAccessException {

		List<Sillon> lstSillons = jdbcTemplate.query(REQ_SELECT, mapperSillon);
		if (lstSillons.size() == 0)
			throw new EmptyResultDataAccessException(0);
		return lstSillons;
	}

	/**
	 * Récupération d'un sillon à partir de son identifiant interne.
	 */
	@Override
	public Sillon getById(int id) throws DataAccessException {

		return jdbcTemplate.queryForObject(REQ_SELECT_BY_ID,
				new Object[] { id }, mapperSillon);
	}

	/**
	 * Récupération de la liste des sillons pour une date donnée et
	 * éventuellement une plage horaire. Ici, on récupère l'ensemble des
	 * sillons, que le relevé de composition soit arrivé ou non.
	 */
	@Override
	public List<Sillon> getListByDateAndTime(Date date, Date dateEnd,
			String time, String timeEnd, String type)
			throws DataAccessException {

		List<Sillon> lstSillons = null;

		if (HubSillonType.ALL.getValue().equals(type)) {
			lstSillons = jdbcTemplate.query(REQ_SELECT_BY_DATE, new Object[] {
					date, dateEnd, time, timeEnd }, mapperSillon);

		} else {
			lstSillons = jdbcTemplate.query(REQ_SELECT_BY_DATE_TYPE,
					new Object[] { date, dateEnd, time, timeEnd, type },
					mapperSillon);
		}
		if (lstSillons.size() == 0)
			throw new EmptyResultDataAccessException(0);
		return lstSillons;
	}

	/**
	 * Récupération d'un sillon à partir de son identifiant externe et de sa
	 * date.
	 */
	@Override
	public Sillon getByExtIdAndDate(String sillonExtId, Date dateSillon)
			throws DataAccessException {

		return jdbcTemplate.queryForObject(REQ_SELECT_BY_EXT_ID_DATE,
				new Object[] { sillonExtId, dateSillon, dateSillon },
				mapperSillon);
	}

	/**
	 * Récupération d'un sillon à partir du tcms train id.
	 */
	@Override
	public Sillon getByTcmsTrainId(String tcmsTrainId)
			throws DataAccessException {

		return jdbcTemplate.queryForObject(REQ_SELECT_BY_TCMS_TRAIN_ID,
				new Object[] { tcmsTrainId.toUpperCase() }, mapperSillon);
	}

	/**
	 * Intégration des sillons à partir d'un fichier reçu par TCMS. Il faut
	 * absolument éviter qu'un sillon soit réinjecté par erreur alors qu'il est
	 * déjà en cours de traitement (par exemple le coordinateur qui réinjecte le
	 * fichier de la veille par erreur). On recherche donc le sillon, s'il est
	 * trouvé, il faut que le statut soit au maximum à "Attribué". Sinon on ne
	 * fait rien.
	 */
	@Override
	@Transactional
	public void integrate(List<Sillon> lstSillons) throws DataAccessException {

		Sillon sillonBase;

		for (Sillon sillon : lstSillons) {

			try {
				sillonBase = jdbcTemplate.queryForObject(
						REQ_SELECT_BY_TCMS_TRAIN_ID,
						new Object[] { sillon.getTcmsTrainId() }, mapperSillon);
			} catch (EmptyResultDataAccessException e) {
				sillonBase = null;
			}

			if (sillonBase == null) {

				// Creation
				jdbcTemplate.update(
						REQ_INSERT,
						new Object[] { sillon.getExtId(),
								sillon.getMovementDate(),
								sillon.getMovementTime(),
								sillon.getRealMovementDate(),
								sillon.getRealMovementTime(),
								sillon.getTcmsTrainId(), sillon.getOrigin(),
								sillon.getDestination(), sillon.getStatusId(),
								sillon.getType(), sillon.getNbWagons(),
								sillon.isEcrOwner(), sillon.getComment() });

				// On ne met à jour le sillon que si le train n'est pas encore
				// arrivé ou s'il est en statut "Annulé" (au cas ou
				// on pourrait avoir une remise en vigeur du sillon).
			} else if (HubSillonStatus.COMPO_PLAN_ARRIVED_TRAIN.getValue() > sillonBase
					.getStatusId()
					|| HubSillonStatus.CANCELED.getValue() == sillonBase
							.getStatusId()) {

				// On conserve le statut existant.
				sillon.setStatusId(sillonBase.getStatusId());

				// Mise à jour
				jdbcTemplate.update(
						REQ_UPDATE_BY_TCMS_TRAIN_ID,
						new Object[] { sillon.getMovementDate(),
								sillon.getMovementTime(),
								sillon.getTcmsTrainId(), sillon.getOrigin(),
								sillon.getDestination(), sillon.getExtId(),
								sillon.getNbWagons(), sillon.getStatusId(),
								sillon.getType(), sillon.getRealMovementDate(),
								sillon.getRealMovementTime(),
								HubObjectActive.ACTIVE.getValue(),
								sillon.isEcrOwner(), sillon.getComment(),
								sillon.getTcmsTrainId() });
			}
		}
	}

	/**
	 * Récupération de l'ensemble des sillons à l'arrivée sur le Hub. On prend
	 * tous les sillons dont le relevé de composition a été réçu et dont la
	 * statut sillon n'est pas encore à "8 reception train terminée".
	 */
	@Override
	public List<Sillon> getListForArrival() throws DataAccessException {

		List<Sillon> lstSillons = jdbcTemplate.query(REQ_SELECT_ARRIVAL,
				mapperSillon);
		if (lstSillons.size() == 0)
			throw new EmptyResultDataAccessException(0);
		return lstSillons;
	}

	/**
	 * Récupération de l'ensemble des sillons au départ pour le hub.
	 */
	@Override
	public List<Sillon> getListForDeparture() throws DataAccessException {

		List<Sillon> lstSillons = jdbcTemplate.query(REQ_SELECT_DEPARTURE,
				mapperSillon);
		if (lstSillons.size() == 0)
			throw new EmptyResultDataAccessException(0);
		return lstSillons;
	}

	/**
	 * Mise à jour d'un commentaire pour un sillon, que le sillon soit à
	 * l'arrivée ou au départ. On met à jour le sillon à partir de son
	 * identifiant interne.
	 */
	@Override
	public void updateComment(Sillon sillon) throws DataAccessException {

		jdbcTemplate.update(REQ_UPDATE_COMMENT,
				new Object[] { sillon.getComment(), sillon.getId() });
	}

	/**
	 * Mise à jour d'un sillon.
	 */
	@Override
	@Transactional
	public void update(Sillon sillon) throws DataAccessException {

		jdbcTemplate.update(
				REQ_UPDATE,
				new Object[] { sillon.getStatusId(), sillon.isReverseSense(),
						sillon.getRailId(), sillon.getRailExtId(),
						sillon.getRealMovementDate(),
						sillon.getRealMovementTime(),
						sillon.getRailFormationId(),
						sillon.getRailFormationExtId(), sillon.getNbWagons(),
						sillon.getLading(), sillon.getRailLabel(),
						sillon.getRailFormationLabel(),
						sillon.getCompositionFileName(), sillon.isEcrOwner(),
						sillon.getId() });
	}

	/**
	 * Historisation de la table des sillons. Comme il y a peu d'enregistrements
	 * on fait une boucle, par ailleurs il faut pouvoir récupérer l'ensemble des
	 * sillons pour pouvoir obtenir les compositions à effacer. Sin on arrive
	 * pas à transférer une ligne, alors on annule tout.
	 */
	@Override
	@Transactional
	public List<Sillon> historize(Date date) throws DataAccessException {

		List<Sillon> lstSillons = jdbcTemplate.query(REQ_HISTO_SELECT,
				new Object[] { date }, mapperSillon);
		for (Sillon sillon : lstSillons) {
			int nbUpdate = jdbcTemplate.update(
					REQ_HISTO_MOVE,
					new Object[] { sillon.getId(), sillon.getType(),
							sillon.getExtId(), sillon.getOrigin(),
							sillon.getDestination(), sillon.getMovementDate(),
							sillon.getRealMovementDate(),
							sillon.getMovementTime(),
							sillon.getRealMovementTime(),
							sillon.getTcmsTrainId(), sillon.getRailId(),
							sillon.getRailExtId(), sillon.getRailLabel(),
							sillon.getRailFormationId(),
							sillon.getRailFormationExtId(),
							sillon.getRailFormationLabel(),
							sillon.getStatusId(), sillon.isReverseSense(),
							sillon.getNbWagons(), sillon.getLading(),
							sillon.getComment(),
							sillon.getCompositionFileName(),
							sillon.getDateUpdate(), sillon.isUpdated(),
							sillon.isEcrOwner() });

			int nbDelete = jdbcTemplate.update(REQ_HISTO_DEL,
					new Object[] { sillon.getId() });

			if (nbUpdate == 0 || nbDelete == 0)
				throw new DaoCopyException(
						"Hub : Impossible de transférer la ligne dans la table d'historique.");
		}
		return lstSillons;
	}
}